import { useCallback, useEffect, useMemo, useReducer, useState } from 'react';
import useLockFn from '../useLockFn';
import useMemoizedFn from '../useMemoizedFn';

export type AnyObject = Record<string, any>;
export interface OperateResponse {
  failed: boolean;
}
export interface ListResponse<T> {
  page: number;
  size: number;
  total: number;
  list: T[];
}
export interface Pagination {
  page: number;
  size: number;
  total: number;
}
export type GetListRequest<T> = (
  page: number,
  size: number,
  params?: AnyObject,
) => Promise<ListResponse<T>>;
export interface Options {
  manual: boolean;
  /**
   * 数据插入方式 - 首位插入、尾部插入
   *
   * @unshift 首位插入， 新增数据查询第一页数据
   * @push 尾部插入， 新增数据查询最后一页数据
   */
  dataInsertionWay: 'unshift' | 'push';
  initParams: AnyObject;
  initPagination: Partial<Pagination>;
  add: (params?: AnyObject) => Promise<OperateResponse>;
  edit: (params?: AnyObject) => Promise<OperateResponse>;
  remove: (params?: AnyObject) => Promise<OperateResponse>;
  enable: (params?: AnyObject) => Promise<OperateResponse>;
  disable: (params?: AnyObject) => Promise<OperateResponse>;
}
export interface TableData<T> {
  list: T[];
  pagination: Pagination;
  params: AnyObject;
}
export interface State<T> extends TableData<T> {
  /** 列表请求 loading 状态 */
  isGetting: boolean;
  /** 列表操作 loading 状态 */
  isOperating: boolean;
  initPagination: Pagination;
  initParams: AnyObject;
}
export interface Actions {
  getList: (page: number, size: number, params?: AnyObject) => Promise<void>;
  add: (params?: AnyObject) => Promise<void>;
  edit: (params?: AnyObject) => Promise<void>;
  remove: (params?: AnyObject) => Promise<void>;
  enable: (params?: AnyObject) => Promise<void>;
  disable: (params?: AnyObject) => Promise<void>;
  onReset: (params?: AnyObject) => void;
  onSearch: (params?: AnyObject) => void;
  onChangeParams: (params: AnyObject, manual?: boolean) => void;
  onChangePagination: (
    page: number,
    pageSize: number,
    params?: AnyObject,
  ) => void;
}

const SET_STATE = 'SET_STATE';
const SET_LIST = 'SET_LIST';
const SET_PAGINATION = 'SET_PAGINATION';
const SET_PARAMS = 'SET_PARAMS';
const defaultPagination: Pagination = { page: 1, size: 10, total: 0 };
function reducer<T>(state: TableData<T>, action: any) {
  switch (action.type) {
    case SET_STATE:
      return { ...state, ...action.state };
    case SET_LIST:
      return { ...state, ...action.list };
    case SET_PAGINATION:
      return {
        ...state,
        pagination: { ...state.pagination, ...action.pagination },
      };
    case SET_PARAMS:
      return { ...state, params: { ...state.params, ...action.params } };
    default:
      return state;
  }
}
function useTable<T>(
  services: GetListRequest<T>,
  options: Partial<Options> = {},
): [State<T>, Actions] {
  const { dataInsertionWay = 'push' } = options;

  const getListRequest = useMemoizedFn(services);
  const { manual = true, initParams, initPagination } = options;
  const initState: TableData<T> = useMemo(
    () => ({
      list: [],
      pagination: { ...defaultPagination, ...initPagination },
      params: { ...initParams },
    }),
    [],
  );

  const [isGetting, setIsGetting] = useState(false);
  const [isOperating, setIsOperating] = useState(false);
  const [state, dispatch] = useReducer(reducer, initState);

  const getList = useLockFn(
    async (page: number, size: number, params?: AnyObject) => {
      console.log(page, size, 'res');
      setIsGetting(true);
      const res = await getListRequest(page, size, params);

      if (res) {
        const { list, page, size, total } = res;
        dispatch({
          type: SET_STATE,
          state: { list, params, pagination: { page, size, total } },
        });
      }
      setIsGetting(false);
    },
  );

  useEffect(() => {
    const { pagination, params } = initState;
    !manual &&
      pagination.page &&
      pagination.size &&
      getList(pagination.page, pagination.size, params);
  }, [
    initState.pagination.page,
    initState.pagination.size,
    initState.params,
    manual,
  ]);

  const operate = (
    type: string,
    pagination: Pagination,
    listParams?: AnyObject,
  ) => {
    return async (params?: AnyObject) => {
      setIsOperating(true);
      // @ts-ignore
      const res = await options[type]?.(params);
      if (res && !res.failed) {
        getList(pagination.page, pagination.size, listParams);
      }
      setIsOperating(false);
      return res;
    };
  };

  const add = useCallback(
    (() => {
      const pagination: Pagination = { ...initState.pagination };
      if (dataInsertionWay === 'push') {
        const { size, total } = state.pagination;
        pagination.page = Math.ceil((total + 1) / size);
        pagination.size = size;
      } else {
        const { page } = initState.pagination;
        const { size } = state.pagination;
        pagination.page = page;
        pagination.size = size;
      }
      return operate('add', pagination, initState.params);
    })(),
    [dataInsertionWay, state.pagination, initState.pagination],
  );
  const edit = operate('edit', state.pagination, state.params);
  const remove = operate('remove', state.pagination, state.params);
  const enable = operate('enable', state.pagination, state.params);
  const disable = operate('disable', state.pagination, state.params);

  const onReset = (restParams?: AnyObject) => {
    const { pagination, params } = initState;
    getList(pagination.page, pagination.size, { ...params, ...restParams });
  };
  const onSearch = (params?: AnyObject) => {
    const { pagination } = state;
    getList(pagination.page, pagination.size, params);
  };
  const onChangeParams = (params: AnyObject, manual = true) => {
    if (!manual) {
      const { pagination } = state;
      getList(pagination.page, pagination.size, params);
    } else {
      dispatch({ type: SET_PARAMS, params });
    }
  };
  const onChangePagination = (
    page: number,
    size: number,
    params?: AnyObject,
  ) => {
    getList(page, size, { ...state.params, ...params });
  };

  return [
    {
      isGetting,
      isOperating,
      initPagination: initState.pagination,
      initParams: initState.params,
      ...state,
    },
    {
      getList: useMemoizedFn(getList),
      add: useMemoizedFn(add),
      edit: useMemoizedFn(edit),
      remove: useMemoizedFn(remove),
      enable: useMemoizedFn(enable),
      disable: useMemoizedFn(disable),
      onReset: useMemoizedFn(onReset),
      onSearch: useMemoizedFn(onSearch),
      onChangeParams: useMemoizedFn(onChangeParams),
      onChangePagination: useMemoizedFn(onChangePagination),
    },
  ];
}

export default useTable;
